/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package station3d.graphics;

import com.sun.opengl.util.BufferUtil;
import java.io.*;
import java.util.*;
import java.nio.*;
import station3d.ResourceError;

/**
 * 3D model.
 * @author Martin
 */
public class Mesh {

    private final FloatBuffer vertexArray;
    private final FloatBuffer texCoordArray;
    private final FloatBuffer normalArray;

    private final FloatBuffer texCoordArray2;

    private final int verticesCount;


    private Mesh(int verticesCount,
            float[] vertexArray,
            float[] texCoordArray,
            float[] normalArray,
            float[] texCoordArray2)
    {
        
        this.verticesCount = verticesCount;

        this.vertexArray = BufferUtil.newFloatBuffer(vertexArray.length);
        this.vertexArray.put(vertexArray);
        this.vertexArray.rewind();

        if (texCoordArray != null) {
            this.texCoordArray = BufferUtil.newFloatBuffer(texCoordArray.length);
            this.texCoordArray.put(texCoordArray);
            this.texCoordArray.rewind();
        }
        else this.texCoordArray = null;

        if (texCoordArray2 != null) {
            this.texCoordArray2 = BufferUtil.newFloatBuffer(texCoordArray2.length);
            this.texCoordArray2.put(texCoordArray2);
            this.texCoordArray2.rewind();
        }
        else this.texCoordArray2 = null;

        this.normalArray = BufferUtil.newFloatBuffer(normalArray.length);
        this.normalArray.put(normalArray);
        this.normalArray.rewind();
    }

    
    public void dispose() {

    }
    
    public int getVerticesCount() {
        return verticesCount;
    }

    public FloatBuffer getVertexArray() {
        return vertexArray;
    }

    public boolean hasTexCoordArray() {
        return texCoordArray != null;
    }

    public FloatBuffer getTexCoordArray() {
        return texCoordArray;
    }

    public FloatBuffer getNormalArray() {
        return normalArray;
    }

    public boolean hasTexCoordArray2() {
        return texCoordArray2 != null;
    }

    public FloatBuffer getTexCoordArray2() {
        return texCoordArray2;
    }

    /**
     * Načte mesh z .OBJ souboru. V úvahu jsou brány pouze příkazy v, vn, vt a f
     * a .OBJ soubor musí obsahovat normály! Souřadnice textury jsou volitelné.
     *
     * Kromě načítání obyčejných objektů je tato metoda uzpůsobená k načítání
     * terénu. V tomto případě se nastaví parametr calcTexureCoords na true,
     * čímž se zapne automatické vygenerování souřadnic textury. Jelikož terén
     * je vykreslován se dvěma texturami - s jednou velkou (udávající barvu) -
     * a s druhou pro vytvoření detailů, je třeba zadat parametr texture2Scale,
     * kterým se určí škála druhé textury.
     *
     * @param is vstupní proud zdroje
     * @param doubleFaces udává, zda se mají zdvojit všechny polygony (užitečné pro stromy)
     * @param calcTextureCoords udává, zda se mají vypočítat souřadnice textury (plošné mapování)
     * @param texture2Scale škála souřadnic pro druhou texturu
     * @return vrátí nově vytvořený mesh
     * @throws IOException
     */
    public static Mesh loadFromFile(InputStream is, boolean doubleFaces, boolean calcTextureCoords, float texture2Scale) throws IOException {

        ArrayList<Float> vertices = new ArrayList<Float>();  // pole vrcholů
        ArrayList<Float> texCoords = new ArrayList<Float>(); // pole souřadnic textur
        ArrayList<Float> normals = new ArrayList<Float>();   // pole normál

        ArrayList<Integer> meshIndices = new ArrayList<Integer>(); // indexy do předchozích polí

        boolean hasTextureCoords = true;

         // pro generování souřadnic textury na ploše
        float minX = 0, maxX = 0;
        float minZ = 0, maxZ = 0;

        InputStreamReader r = new InputStreamReader(is);
        BufferedReader bf = new BufferedReader(r);

        String line;
        while ((line = bf.readLine()) != null) {
            if (line.startsWith("#")) continue; // ignorovat komentáře

            String[] tokens = line.split("\\s");

            // vrchol
            if (tokens[0].equals("v")) {
                float x, y, z;
                x = Float.parseFloat(tokens[1]);
                y = Float.parseFloat(tokens[2]);
                z = Float.parseFloat(tokens[3]);

                // přidat vrchol
                vertices.add(x);
                vertices.add(y);
                vertices.add(z);

                if (x < minX) minX = x;
                if (x > maxX) maxX = x;
                if (z < minZ) minZ = z;
                if (z > maxZ) maxZ = z;
            }

            // normála
            else if (tokens[0].equals("vn")) {
                float x, y, z;
                x = Float.parseFloat(tokens[1]);
                y = Float.parseFloat(tokens[2]);
                z = Float.parseFloat(tokens[3]);

                // přidat normálu
                normals.add(x);
                normals.add(y);
                normals.add(z);
            }

            // souřadnice textury
            else if (tokens[0].equals("vt")) {
                float u, v;
                u = Float.parseFloat(tokens[1]);
                v = Float.parseFloat(tokens[2]);

                // přidat souřadnice textury
                texCoords.add(u);
                texCoords.add(v);
            }

            // polygon - musí být trojúhelník!
            else if (tokens[0].equals("f")) {
                for (int i = 1; i <= 3; i++) {
                    int vertex, texCoord, normal;
                    texCoord = -1;
                    normal = -1;

                    String[] indices = tokens[i].split("/");

                    vertex = Integer.parseInt(indices[0]) - 1;
                    if (!indices[1].equals("")) {
                        texCoord = Integer.parseInt(indices[1]) - 1;
                    }
                    normal = Integer.parseInt(indices[2]) - 1;

                    // chybí-li jen jedna souřadnice, vypnout textury
                    if (texCoord == -1) {
                        hasTextureCoords = false;
                    }

                    meshIndices.add(vertex);
                    meshIndices.add(texCoord);
                    meshIndices.add(normal);
                }
            }
        }


        bf.close();
        

        // Čtení ze souboru je hotové, zkonstruovat OpenGL pole
        int nVertices = meshIndices.size() / 3;
        int arraySize = nVertices;
        if (doubleFaces) arraySize *= 2;
        float[] vertexArray = new float[3*arraySize];
        float[] texCoordArray = null;
        float[] texCoordArray2 = null;
        if (hasTextureCoords || calcTextureCoords) {
            texCoordArray = new float[2*arraySize];
            if (calcTextureCoords) {
                texCoordArray2 = new float[2*arraySize];
            }
        }
        float[] normalArray = new float[3*arraySize];

        // Naplnit pole
        for (int i = 0; i < nVertices; i++) {
            vertexArray[3*i] = vertices.get(3*meshIndices.get(3*i));
            vertexArray[3*i+1] = vertices.get(3*meshIndices.get(3*i)+1);
            vertexArray[3*i+2] = vertices.get(3*meshIndices.get(3*i)+2);

            if (hasTextureCoords) {
                texCoordArray[2*i] = texCoords.get(2*meshIndices.get(3*i+1));
                texCoordArray[2*i+1] = 1 - texCoords.get(2*meshIndices.get(3*i+1)+1);
            }
            else if (calcTextureCoords) {
                float w = maxX - minX;
                float posX = vertexArray[3*i] - minX;
                texCoordArray[2*i] = (posX / w);
                texCoordArray2[2*i] = (posX / w)*texture2Scale;
                float h = maxZ - minZ;
                float posZ = vertexArray[3*i+2] - minZ;
                texCoordArray[2*i+1] = (posZ / h);
                texCoordArray2[2*i+1] = (posZ / h)*texture2Scale;
            }


            normalArray[3*i] = normals.get(3*meshIndices.get(3*i+2));
            normalArray[3*i+1] = normals.get(3*meshIndices.get(3*i+2)+1);
            normalArray[3*i+2] = normals.get(3*meshIndices.get(3*i+2)+2);
        }

        // duplikovat trojúhelníky
        if (doubleFaces) {
            for (int i = 0; i < nVertices; i += 3) {
                vertexArray[3*nVertices+3*i] = vertexArray[3*i];
                vertexArray[3*nVertices+3*i+1] = vertexArray[3*i+1];
                vertexArray[3*nVertices+3*i+2] = vertexArray[3*i+2];

                vertexArray[3*nVertices+3*i+3] = vertexArray[3*i+6];
                vertexArray[3*nVertices+3*i+4] = vertexArray[3*i+7];
                vertexArray[3*nVertices+3*i+5] = vertexArray[3*i+8];

                vertexArray[3*nVertices+3*i+6] = vertexArray[3*i+3];
                vertexArray[3*nVertices+3*i+7] = vertexArray[3*i+4];
                vertexArray[3*nVertices+3*i+8] = vertexArray[3*i+5];
            

                if (hasTextureCoords) {
                    texCoordArray[2*nVertices+2*i] = texCoordArray[2*i];
                    texCoordArray[2*nVertices+2*i+1] = texCoordArray[2*i+1];

                    texCoordArray[2*nVertices+2*i+2] = texCoordArray[2*i+4];
                    texCoordArray[2*nVertices+2*i+3] = texCoordArray[2*i+5];

                    texCoordArray[2*nVertices+2*i+4] = texCoordArray[2*i+2];
                    texCoordArray[2*nVertices+2*i+5] = texCoordArray[2*i+3];
                }

            
                normalArray[3*nVertices+3*i] = -normalArray[3*i];
                normalArray[3*nVertices+3*i+1] = -normalArray[3*i+1];
                normalArray[3*nVertices+3*i+2] = -normalArray[3*i+2];

                normalArray[3*nVertices+3*i+3] = -normalArray[3*i+6];
                normalArray[3*nVertices+3*i+4] = -normalArray[3*i+7];
                normalArray[3*nVertices+3*i+5] = -normalArray[3*i+8];

                normalArray[3*nVertices+3*i+6] = -normalArray[3*i+3];
                normalArray[3*nVertices+3*i+7] = -normalArray[3*i+4];
                normalArray[3*nVertices+3*i+8] = -normalArray[3*i+5];
            }

            nVertices *= 2;
        }

        Mesh mesh = new Mesh(nVertices, vertexArray, texCoordArray, normalArray, texCoordArray2);

        return mesh;
    }


}
